﻿using System;
using System.Collections.Generic;
using System.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Plugins;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a sales rule.
    /// </summary>
    /// <typeparam name="TRule">The concrete rule type.</typeparam>
    public abstract class CreateSalesRule<TRule> : CreateAuditable<TRule>,
        ISecuredOperation, IApiOperation
        where TRule : SalesRuleEntity
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        protected CreateSalesRule()
        {
            ValidFrom = DateTime.UtcNow;
            ValidTo = DateTime.UtcNow.AddMonths(1);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public virtual IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the rule name.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the common flags.
        /// </summary>
        public SalesRuleFlags CommonFlags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the validity date 'from'.
        /// </summary>
        public DateTime ValidFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the validity date 'to'.
        /// </summary>
        public DateTime ValidTo
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Construct(IUnitOfWork uow, TRule entity)
        {
            base.Construct(uow, entity);

            entity.Name = StringHelper.EnsureNotNull(Name).TrimToLen(100);
            entity.CommonFlags = CommonFlags;
            entity.ValidFrom = ValidFrom;
            entity.ValidTo = ValidTo;
        }

        #endregion
    }

    /// <summary>
    /// Updates the sales rule.
    /// </summary>
    /// <typeparam name="TRule">The concrete rule type.</typeparam>
    public abstract class UpdateSalesRule<TRule> : UpdateAuditable<TRule>,
        ISecuredOperation, IApiOperation
        where TRule : SalesRuleEntity
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        protected UpdateSalesRule()
        {
            ValidFrom = DateTime.MinValue;
            ValidTo = DateTime.MaxValue;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public virtual IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the rule name.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public SalesRuleFlags CommonFlags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the validity date 'from'.
        /// </summary>
        public DateTime ValidFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the validity date 'to'.
        /// </summary>
        public DateTime ValidTo
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Revise(IUnitOfWork uow, TRule entity)
        {
            base.Revise(uow, entity);

            entity.Name = StringHelper.EnsureNotNull(Name).TrimToLen(100);
            entity.CommonFlags = CommonFlags;
            entity.ValidFrom = ValidFrom;
            entity.ValidTo = ValidTo;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the sales rule.
    /// </summary>
    public class DeleteSalesRule : DeleteEntity<SalesRuleEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion

        #region Utilities

        protected override void Destruct(IUnitOfWork uow, SalesRuleEntity entity)
        {
            foreach(var i in entity.Roles)
            {
                uow.Execute(new UnmapSalesRuleFromRole
                {
                    EntityID = entity.EntityID,
                    ChildID = i.EntityID
                });
            }
            base.Destruct(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Gets the sales rule.
    /// </summary>
    public class GetSalesRule : GetEntity<SalesRuleEntity>,
        IApiOperation
    {
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over sales rule collection.
    /// </summary>
    public class SearchSalesRules : SearchOperation<SalesRuleEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchSalesRules()
        {
            Term = SearchTerm.Empty;
            SortBy = new []
            {
                SalesRuleSortBy.ID
            };
            SortDir = SortDirection.Ascending;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the required validity date.
        /// </summary>
        public DateTime? ValidOn
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public SalesRuleFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public SalesRuleSortBy[] SortBy
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SalesRuleEntity> Build(IUnitOfWork uow, IQueryable<SalesRuleEntity> query)
        {
            query = base.Build(uow, query);
            query = query.FilterByFlags(x => (int)x.CommonFlags, (int)FlagMask);
            if(ValidOn.HasValue)
                query = query.Where(x => x.ValidFrom <= ValidOn.Value && x.ValidTo >= ValidOn.Value);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.Name.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.Name.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.Name.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.Name.Equals(Term.Keyword));
                    break;
            }
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case SalesRuleSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        case SalesRuleSortBy.Name:
                            query = query.OrderBy(x => x.Name, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    /// <summary>
    /// Gets the collection of rules as a dictionary, where 
    /// key - rule identifier and value - rule name.
    /// </summary>
    public class GetSalesRuleDictionary : SearchOperation<SalesRuleEntity, Int32, String>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public GetSalesRuleDictionary()
        {
            Term = SearchTerm.Empty;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public SalesRuleFlags FlagMask
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SalesRuleEntity> Build(IUnitOfWork uow, IQueryable<SalesRuleEntity> query)
        {
            query = base.Build(uow, query);
            query = query.FilterByFlags(x => (int)x.CommonFlags, (int)FlagMask);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.Name.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.Name.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x =>
                        x.Name.Contains(Term.Keyword) ||
                        x.Name.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.Name.Equals(Term.Keyword));
                    break;
            }
            query = query.OrderBy(x => x.Name);
            return query;
        }

        protected override IQueryable<KeyValuePair<Int32, String>> Map(IQueryable<SalesRuleEntity> query)
        {
            return query.Select(x => new KeyValuePair<Int32, String>(x.EntityID, x.Name));
        }

        #endregion
    }

    #endregion

    #region __Map__

    /// <summary>
    /// Maps the rule to role.
    /// </summary>
    public class MapSalesRuleToRole : MapOperation<SalesRuleEntity, RoleEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion
    }

    /// <summary>
    /// Unmaps the rule from role.
    /// </summary>
    public class UnmapSalesRuleFromRole : UnmapOperation<SalesRuleEntity, RoleEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion
    }

    #endregion

    #region __Workflow__

    /// <summary>
    /// Applies the sales rule to an order.
    /// </summary>
    public class ApplySalesRuleToOrder : Operation<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public virtual IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the rule identifier.
        /// </summary>
        public int RuleID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the order identifier.
        /// </summary>
        public int OrderID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            var rule = uow.Execute(new GetSalesRule
            {
                ThrowsException = true,
                EntityID = RuleID
            }).Result;
            if(!rule.CommonFlags.HasFlag(SalesRuleFlags.OrderScope))
                return Err.Of<SalesRuleEntity>().NotApplicable();
            if(!rule.CommonFlags.HasFlag(SalesRuleFlags.Active))
                return Err.Of<SalesRuleEntity>().NotAvailable();
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            var rule = uow.Execute(new GetSalesRule
            {
                ThrowsException = true,
                EntityID = RuleID
            }).Result;
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = OrderID
            }).Result;

            decimal discountAmount = Decimal.Zero;
            decimal taxAmount = Decimal.Zero;
            decimal handlingFee = Decimal.Zero;
            decimal shippingPrice = Decimal.Zero;
            decimal total = Decimal.Zero;
            int applyRet = 0;

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                applyRet = (ioc.Resolve(rule.HandlerID) as ISalesRuleHandler).Apply(uow,
                    rule,
                    order,
                    out discountAmount,
                    out handlingFee,
                    out shippingPrice,
                    out taxAmount,
                    out total);
            }
            if(applyRet < 0)
                throw Err.Of<ISalesRuleHandler>().Code(applyRet).Ex();
            if(applyRet > 0)
            {
                order = uow.Execute(new UpdateOrder
                {
                    EntityID = order.EntityID,
                    DiscountAmount = discountAmount,
                    TaxAmount = taxAmount,
                    HandlingFee = handlingFee,
                    ShippingPrice = shippingPrice,
                    Subtotal = order.Subtotal,
                    Total = total,
                    Flags = order.Flags
                }).Result;

                uow.Execute(new CreateSalesRuleHistory
                {
                    RuleID = rule.EntityID,
                    OrderID = order.EntityID,
                    ItemID = 0,
                    Flags = SalesRuleHistoryFlags.None
                });
            }

            result = order;
        }

        #endregion
    }

    /// <summary>
    /// Applies the sales rule to an order item.
    /// </summary>
    public class ApplySalesRuleToOrderItem : Operation<OrderItemEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public virtual IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the rule identifier.
        /// </summary>
        public int RuleID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the item identifier.
        /// </summary>
        public int ItemID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            var rule = uow.Execute(new GetSalesRule
            {
                ThrowsException = true,
                EntityID = RuleID
            }).Result;
            if(!rule.CommonFlags.HasFlag(SalesRuleFlags.ItemScope))
                return Err.Of<SalesRuleEntity>().NotApplicable();
            if(!rule.CommonFlags.HasFlag(SalesRuleFlags.Active))
                return Err.Of<SalesRuleEntity>().NotAvailable();
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out OrderItemEntity result)
        {
            var rule = uow.Execute(new GetSalesRule
            {
                ThrowsException = true,
                EntityID = RuleID
            }).Result;
            var item = uow.Execute(new GetOrderItem
            {
                ThrowsException = true,
                EntityID = ItemID
            }).Result;

            decimal discountAmount = Decimal.Zero;
            decimal taxAmount = Decimal.Zero;
            decimal total = Decimal.Zero;
            int applyRet = 0;

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                applyRet = (ioc.Resolve(rule.HandlerID) as ISalesRuleHandler).Apply(uow,
                    rule,
                    item,
                    out discountAmount,
                    out taxAmount,
                    out total);
            }
            if(applyRet < 0)
                throw Err.Of<ISalesRuleHandler>().Code(applyRet).Ex();
            if(applyRet > 0)
            {
                item = uow.Execute(new UpdateOrderItem
                {
                    EntityID = item.EntityID,
                    SKU = item.SKU,
                    Price = item.Price,
                    Quantity = item.Quantity,
                    TaxAmount = taxAmount,
                    DiscountAmount = discountAmount,
                    Subtotal = item.Subtotal,
                    Total = total,
                    Flags = item.Flags
                }).Result;

                uow.Execute(new CreateSalesRuleHistory
                {
                    RuleID = rule.EntityID,
                    OrderID = item.Order.EntityID,
                    ItemID = item.EntityID,
                    Flags = SalesRuleHistoryFlags.None
                });
            }
            result = item;
        }

        #endregion
    }

    /// <summary>
    /// Commits the sales rule usage.
    /// </summary>
    public class CommitSalesRule : Operation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the history record identifier.
        /// </summary>
        public int HistoryID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow)
        {
            var history = uow.Execute(new GetSalesRuleHistory
            {
                ThrowsException = true,
                EntityID = HistoryID
            }).Result;

            int commitRet = 0;
            using(var ioc = uow.Infrastructure.DependencyResolver())
                commitRet = (ioc.Resolve(history.Rule.HandlerID) as ISalesRuleHandler).Commit(uow, history);
            if(commitRet < 0)
                throw Err.Of(this).NotPossible().Code(commitRet).Ex();

            uow.Execute(new UpdateSalesRuleHistory
            {
                EntityID = history.EntityID,
                Flags = SalesRuleHistoryFlags.Active
            });
        }

        #endregion
    }

    /// <summary>
    /// Rollbacks the sales rule usage.
    /// </summary>
    public class RollbackSalesRule : Operation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the history record identifier.
        /// </summary>
        public int HistoryID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow)
        {
            var history = uow.Execute(new GetSalesRuleHistory
            {
                ThrowsException = true,
                EntityID = HistoryID
            }).Result;

            using(var ioc = uow.Infrastructure.DependencyResolver())
                (ioc.Resolve(history.Rule.HandlerID) as ISalesRuleHandler).Rollback(uow, history);

            uow.Execute(new UpdateSalesRuleHistory
            {
                EntityID = history.EntityID,
                Flags = SalesRuleHistoryFlags.None
            });
        }

        #endregion
    }

    #endregion
}
